import ij.IJ;
import ij.ImagePlus;
import ij.process.ImageProcessor;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Stack;

/**
*
* Bachelor thesis at the Institute of Biomedical Engineering, 
* located at the UMIT/EWZ campus in Hall in Tyrol. <br><br>
*
* This Class is responsible for the manual correction of the result after
* Alternating Sequential filtering 
* 
* @author Stefan Fuchs
*
*/
public class ManualCorrectionLabeling implements Runnable , ActionListener, WindowListener{

		//int[] 
	/** The old ASF label*/
	private int[] oldASFLabel = null;
	
	/** The new ASF label*/
	private int[] newASFLabel = null;
	
	/** Label that stores the voxels to be deleted*/
	private int[] deletedVoxelsLabel = null;
	
	/** Label that stores the voxels to be added*/
	private int[] addedVoxelsLabel = null;
	
		//int
	/** Height of the Image */
	private int height;

	/** Width of the Image */
	private int width;

	/** Amount of Slices in the stack */
	private int size;

	/** Min Roi-Prameters X-Axis */
	private int minRoiX;

	/** Max Roi-Prameters X-Axis */
	private int maxRoiX;

	/** Min Roi-Prameters Y-Axis */
	private int minRoiY;

	/** Max Roi-Prameters Y-Axis */
	private int maxRoiY;

	/** Min Roi-Prameters Z-Axis */
	private int minRoiZ;

	/** Max Roi-Prameters Z-Axis */
	private int maxRoiZ;
	
		//boolean
	/** Indicator if the thread is running*/
	private boolean running = false;
	
		//String
	/** The name of the ASF image*/
	private String fullImageTitle = null;
	
		//Arraylist
	/** The List of manually added Voxels*/
	private ArrayList<Voxel> manuallyAddedVoxels = null;
	
	/** The List of manually deleted voxels*/
	private ArrayList<Voxel> manuallyDeletedVoxels = null;
	
	/** The List with the ventricle voxels*/
	private ArrayList<Voxel> ventriclePoints = null;
	
		//misc
	/** The statusdialog*/
	private StatusDialog sd;
	
	/** The thread*/
	private Thread thread;
	
	/** The Modell*/
	private Modell modell;
	
	//////////////////////////////////////////////////////////////
	/////////////////// Singeton Part ////////////////////////////
	////////////////////////////////////////////////////////////
	
	private static ManualCorrectionLabeling instance = null;
	
	public static synchronized ManualCorrectionLabeling getInstance() {
		
		if ( instance == null) {
			instance = new ManualCorrectionLabeling();
		}
		return instance;
	}
	
	private ManualCorrectionLabeling() {
		//dummy constructor
	}
	
	////////////////////////////////////////////////////////////
	
	public void prepearManualCorrection() {
		this.running = true;
		this.thread = new Thread(ManualCorrectionLabeling.getInstance());
		this.thread.start();
	}
	
	@Override
	public void run() {
		
		this.modell = Modell.getInstance();
		getDimensionParameters();
		
		this.sd = new StatusDialog();
		this.sd.getBtnAbort().addActionListener(this);
		this.sd.getJfDialog().addWindowListener(this);
	
		prepearDataStructure();
		
		startLabeling();
		
		visualizeManualLabeling();
		
		this.sd.close();
		
		this.modell.setAlternatingSequenceFilteringLabel(this.newASFLabel);
		this.running = false;
		
		clearAll();
	}
	
	/**
	 * Visualize the outcome
	 */
	private void visualizeManualLabeling(){
		
		//Close Image if its opened
		if (ij.WindowManager.getImage(this.fullImageTitle) != null ) {
			if (ij.WindowManager.getImage(this.fullImageTitle).isVisible() ) {
				ij.WindowManager.getImage(this.fullImageTitle).changes = false;
				ij.WindowManager.getImage(this.fullImageTitle).close();
			}
		}
		
		ImagePlus manualImage = new ImagePlus(this.fullImageTitle, this.modell.getImageStack());
		
		ImageProcessor manualImageProc = manualImage.getProcessor();
		
		for ( Voxel v : this.ventriclePoints) {
			manualImage.setSlice(v.getZ());
			manualImageProc.putPixel(v.getX(), v.getY(), v.getValue());
		}
		
		manualImage.setSlice(1);
		manualImage.show();
	}
	
	/**
	 * Prepear the Labeling
	 */
	private void startLabeling() {
		
		this.sd.setTitle("Manuall Correction ...");
		this.sd.setTime("");
		this.sd.show(true);
		
		int coordinate1D;
		
		/*
		 * Add voxels and connect them to the label
		 */
		for ( int z = this.minRoiZ; z < this.maxRoiZ; z++) {
			for ( int y = this.minRoiY; y < this.maxRoiY ; y++ ) {
				for (int x = this.minRoiX; x < this.maxRoiX ; x++ ) {
					this.sd.setStatus("Adding Voxels: " + (int)(100 * (z - this.minRoiZ) / (this.maxRoiZ - this.minRoiZ)) + " %");
					coordinate1D =  (int)(this.width * ( this.height * z + y ) + x);
				
					if ( this.newASFLabel[coordinate1D] == 0 && this.oldASFLabel[coordinate1D] == 1 ) {
						observe(x, y, z, 1);
					}
				
					if ( this.newASFLabel[coordinate1D] == 0 && this.oldASFLabel[coordinate1D] == 2 ){
						observe(x, y, z, 2);
					}
				}
			}
		}
		
		/*
		 * Delete the Voxels if they are specified
		 */
		if (this.manuallyDeletedVoxels.size() > 0 ) {
			for ( int i = 0 ; i < this.deletedVoxelsLabel.length ; i++ ) {
				this.sd.setStatus("Removing Voxels: " + ((int) (100/(double)this.deletedVoxelsLabel.length) * i ) );
				if ( this.deletedVoxelsLabel[i] != 0 ) {
					this.newASFLabel[i] = 0;
				}
			}
		}
		
		//Writing into the Array
		for(int z = this.minRoiZ; z < this.maxRoiZ; z++){
			this.sd.setStatus("Processing Image: " + (int)(100 * (z - this.minRoiZ) / (this.maxRoiZ - this.minRoiZ)) + " %");
			for (int y = this.minRoiY; y < this.maxRoiY; y++){
				for (int x = this.minRoiX; x < this.maxRoiX; x++){
					
					coordinate1D = (int)(this.width * (this.height * z + y) + x);
					
					if (this.newASFLabel[coordinate1D] == 1 ) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.VENTRICLE_A_POINTS_LABEL.getRGB()));
					}
					if (this.newASFLabel[coordinate1D] == 2) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.VENTRICLE_B_POINTS_LABEL.getRGB()));
					}
				}
			}
		}
	}
	
	/**
	 * Connected Component Labeling with Moor neighbourhood
	 * 
	 * @param x
	 * @param y
	 * @param z
	 * @param originalLabel
	 */
	private void observe(int x, int y, int z,int originalLabel) {
		int coordinate1D;
		Stack<Voxel> imageStack = new Stack<Voxel>();
		Voxel tmpVoxel;
		
		imageStack.push(new Voxel(x,y,z,0));
		
		while (!imageStack.isEmpty()) {
			
			tmpVoxel = imageStack.pop();
			
			coordinate1D = (int)(this.width * (this.height * tmpVoxel.getZ() + tmpVoxel.getY() ) + tmpVoxel.getX());
			
			if ( this.newASFLabel[coordinate1D] == 0 && ( this.oldASFLabel[coordinate1D] == originalLabel || this.addedVoxelsLabel[coordinate1D] == 1 ) )  {
				
				this.newASFLabel[coordinate1D] = originalLabel;
				
				//x-Layer 
				if ( tmpVoxel.getX() + 1 < this.width) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1 , tmpVoxel.getY(), tmpVoxel.getZ() , 0));
				}
				if ( tmpVoxel.getX() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1 , tmpVoxel.getY(), tmpVoxel.getZ() , 0));
				}
				//y-Layer
				if ( tmpVoxel.getY() + 1 < this.height) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() + 1 , tmpVoxel.getZ() , 0));
				}
				if ( tmpVoxel.getY() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() - 1 , tmpVoxel.getZ() , 0));
				}
				//z-Layer
				if ( tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() , tmpVoxel.getZ() + 1, 0));
				}
				if ( tmpVoxel.getZ() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() , tmpVoxel.getZ() - 1, 0));
				}
				
				//x-y-Layer
				if ( tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() + 1 < this.height) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1 , tmpVoxel.getY() + 1 , tmpVoxel.getZ(), 0));
				}
				if ( tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1 , tmpVoxel.getY() - 1 , tmpVoxel.getZ(), 0));
				}
				if ( tmpVoxel.getX() - 1  > 0 && tmpVoxel.getY() + 1 < this.height) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1 , tmpVoxel.getY() + 1 , tmpVoxel.getZ(), 0));
				}
				if ( tmpVoxel.getX() - 1 > 0 && tmpVoxel.getY() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1 , tmpVoxel.getY() - 1 , tmpVoxel.getZ(), 0));
				}
				//x-z-Layer
				if (tmpVoxel.getX() + 1 < this.width && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1 , tmpVoxel.getY() , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX()  + 1 < this.width && tmpVoxel.getZ() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1 , tmpVoxel.getY() , tmpVoxel.getZ() - 1, 0));
				}
				if (tmpVoxel.getX() - 1 > 0 && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1 , tmpVoxel.getY() , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX() - 1 > 0 && tmpVoxel.getZ() - 1> 0 ) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1 , tmpVoxel.getY() , tmpVoxel.getZ() - 1, 0));
				}
				//y-z-Layer
				if (tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() + 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() + 1 , tmpVoxel.getZ() - 1, 0));
				}
				if (tmpVoxel.getY() - 1 > 0 && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() - 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getY() -1 > 0 && tmpVoxel.getZ() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() , tmpVoxel.getY() - 1 , tmpVoxel.getZ() - 1, 0));
				}
				//x-y-z-Layer
				if (tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1, tmpVoxel.getY() + 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() -1 > 0 ) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1, tmpVoxel.getY() + 1 , tmpVoxel.getZ() - 1, 0));
				}
				if (tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() - 1 > 0 && tmpVoxel.getZ() + 1 < this.size ) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1, tmpVoxel.getY() - 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX() + 1 < this.width && tmpVoxel.getY() -1  > 0 && tmpVoxel.getZ() - 1  > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() + 1, tmpVoxel.getY() - 1 , tmpVoxel.getZ() - 1, 0));
				}
				if (tmpVoxel.getX() -1  > 0 && tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() + 1 < this.size) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1, tmpVoxel.getY() + 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX() - 1 > 0 && tmpVoxel.getY() + 1 < this.height && tmpVoxel.getZ() - 1 > 0 ) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1, tmpVoxel.getY() + 1 , tmpVoxel.getZ() - 1, 0));
				}
				if (tmpVoxel.getX() - 1 > 0 && tmpVoxel.getY() - 1 > 0 && tmpVoxel.getZ() + 1 < this.size ) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1, tmpVoxel.getY() - 1 , tmpVoxel.getZ() + 1, 0));
				}
				if (tmpVoxel.getX() - 1 > 0 && tmpVoxel.getY() - 1 > 0 && tmpVoxel.getZ() - 1 > 0) {
					imageStack.push(new Voxel(tmpVoxel.getX() - 1, tmpVoxel.getY() - 1 , tmpVoxel.getZ() - 1, 0));
				}
			}
		}
	}
	
	/**
	 * Prepear necessary datastructures
	 */
	private void prepearDataStructure() {
		
		this.ventriclePoints = new ArrayList<Voxel>();
		
		this.oldASFLabel = this.modell.getAlternatingSequenceFilteringLabel();
		
		this.newASFLabel = new int[this.oldASFLabel.length];
		this.deletedVoxelsLabel = new int[this.oldASFLabel.length];
		this.addedVoxelsLabel = new int[this.oldASFLabel.length];
		
		for (int i = 0 ; i < this.newASFLabel.length ; i++) {
			this.newASFLabel[i] = 0;
			this.deletedVoxelsLabel[i] = 0;
			this.addedVoxelsLabel[i] = 0;
		}
		
		System.out.println("asf length: " + newASFLabel.length);
		
		int coordinate1D;
		
		//set the voxels to be added
		for ( Voxel v : this.manuallyAddedVoxels ) {
			coordinate1D =  (int)(this.width * (  this.height * v.getZ() + v.getY() ) +v.getX() );
			this.addedVoxelsLabel[coordinate1D] = 1;
		}
		
		//	set the voxels to be deleted
		for (Voxel v : this.manuallyDeletedVoxels ) {
			coordinate1D =  (int)(this.width * ( this.height * v.getZ() + v.getY() ) +v.getX());
			this.deletedVoxelsLabel[coordinate1D] = 1;
		}
	}
	
	
	/**
	 * Get rid of unnecessary datastructures
	 */
	private void clearAll() {
		this.manuallyAddedVoxels = null;
		this.manuallyDeletedVoxels = null;
		this.oldASFLabel = null;
		this.newASFLabel = null;
		this.addedVoxelsLabel = null;
		this.deletedVoxelsLabel = null;
		this.ventriclePoints = null;
		
		this.modell = null;
		
		IJ.freeMemory();
		System.gc();
	}
	
	////////////////////////
	//Setter & Getters ////
	////////////////////////
	
	
	/**
	 * Reads the dimension-Parameters from the model and sets them accordingly
	 */
	private void getDimensionParameters() {
		this.width = this.modell.getWidth();
		this.height = this.modell.getHeight();
		this.size = this.modell.getSize();
		this.minRoiX = this.modell.getMinRoiX();
		this.maxRoiX = this.modell.getMaxRoiX();
		this.minRoiY = this.modell.getMinRoiY();
		this.maxRoiY = this.modell.getMaxRoiY();
		this.minRoiZ = this.modell.getMinRoiZ();
		this.maxRoiZ = this.modell.getMaxRoiZ();
	}
	
	/**
	 * check if the thread is running
	 */
	public boolean isRunning() {
		return running;
	}
	
	/**
	 * Set the image name
	 * 
	 * @param fullImageTitle
	 */
	public void setFullImageTitle(String fullImageTitle) {
		this.fullImageTitle = fullImageTitle;
	}
	
	/**
	 * Sets the manually added voxels
	 * 
	 * @param manuallyDeletedVoxels
	 */
	public void setManuallyAddedVoxels(ArrayList<Voxel> manuallyAddedVoxels) {
		this.manuallyAddedVoxels = manuallyAddedVoxels;
	}
	
	/**
	 * Sets the manually deleted voxels
	 * 
	 * @param manuallyDeletedVoxels
	 */
	public void setManuallyDeletedVoxels(ArrayList<Voxel> manuallyDeletedVoxels) {
		this.manuallyDeletedVoxels = manuallyDeletedVoxels;
	}
	
	///////////////////////////////////////////////
	
	// ActionListener
	@Override
	@SuppressWarnings("deprecation")
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == this.sd.getBtnAbort()){
			System.gc();
			this.thread.stop();
		}
		this.running = false;
		this.sd.close();	
	}

	// ///////////////////////////////////////////////

	// Windowlistener
	@Override
	public void windowActivated(WindowEvent e) {
		//empty block
	}

	@Override
	public void windowClosed(WindowEvent e) {
		//empty block
	}

	@Override
	@SuppressWarnings("deprecation")
	public void windowClosing(WindowEvent e) {
		if(e.getSource() == this.sd.getBtnAbort()){
			System.gc();
			this.thread.stop();
		}
		this.running = false;
		this.sd.close();	
	}

	@Override
	public void windowDeactivated(WindowEvent e) {
		//empty block
	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		//empty block
	}

	@Override
	public void windowIconified(WindowEvent e) {
		//empty block
	}

	@Override
	public void windowOpened(WindowEvent e) {
		//empty block
	}
}